from genQC.imports import *
import genQC.utils.misc_utils as util
from genQC.dataset.config_dataset import ConfigDataset
from genQC.pipeline.multimodal_diffusion_pipeline import MultimodalDiffusionPipeline_ParametrizedCompilation
from genQC.scheduler.scheduler_dpm import DPMScheduler
from genQC.platform.tokenizer.circuits_tokenizer import CircuitTokenizer
from genQC.platform.simulation import Simulator, CircuitBackendType
from genQC.inference.sampling import decode_tensors_to_backend, generate_compilation_tensors
from genQC.inference.evaluation_helper import get_unitaries
from genQC.inference.eval_metrics import UnitaryInfidelityNorm
from genQC.benchmark.bench_compilation import SpecialUnitaries
import genQC.platform.tokenizer.tensor_tokenizer as gpe
Quantum Fourier transform and gate-pair tokenization
# clean existing memory alloc
util.MemoryCleaner.purge_mem() = util.infer_torch_device() # use cuda if we can
device device
[INFO]: Cuda device has a capability of 8.6 (>= 8), allowing tf32 matmul.
device(type='cuda')
# We set a seed to pytorch, numpy and python.
# Note: This will also set deterministic algorithms, possibly at the cost of reduced performance!
0) util.set_seed(
Load model
Load the pre-trained model directly from Hugging Face: Floki00/cirdit_multimodal_compile_3to5qubit.
= MultimodalDiffusionPipeline_ParametrizedCompilation.from_pretrained("Floki00/cirdit_multimodal_compile_3to5qubit", device) pipeline
The model is trained with the gate set:
pipeline.gate_pool
['h', 'cx', 'ccx', 'swap', 'rx', 'ry', 'rz', 'cp']
which we need in order to define the vocabulary
, allowing us to decode tokenized circuits.
= {g:i+1 for i, g in enumerate(pipeline.gate_pool)}
vocabulary = CircuitTokenizer(vocabulary)
tokenizer tokenizer.vocabulary
{'h': 1, 'cx': 2, 'ccx': 3, 'swap': 4, 'rx': 5, 'ry': 6, 'rz': 7, 'cp': 8}
Set inference parameters
Set diffusion model inference parameters.
= DPMScheduler.from_scheduler(pipeline.scheduler)
pipeline.scheduler = DPMScheduler.from_scheduler(pipeline.scheduler_w)
pipeline.scheduler_w
= 40
timesteps
pipeline.scheduler.set_timesteps(timesteps)
pipeline.scheduler_w.set_timesteps(timesteps)
= 1.5
pipeline.lambda_h = 0.45
pipeline.lambda_w = 0.4
pipeline.g_h = 0.2
pipeline.g_w
# These parameters are specific to our pre-trained model.
= 5
system_size = 32 max_gates
For evaluation, we also need a circuit simulator backend.
= Simulator(CircuitBackendType.QISKIT) simulator
Compile the QFT unitary
We now compile the 4-qubit QFT.
= 512
samples = 4
num_of_qubits = f"Compile {num_of_qubits} qubits using: ['h', 'cx', 'ccx', 'swap', 'rx', 'ry', 'rz', 'cp']"
prompt
= SpecialUnitaries.QFT(num_of_qubits).to(torch.complex64) U
= generate_compilation_tensors(pipeline,
out_tensor, params =prompt,
prompt=U,
U=samples,
samples=system_size,
system_size=num_of_qubits,
num_of_qubits=max_gates,
max_gates=False, # show progress bar
no_bar=256, # limit batch size for less GPU memory usage
auto_batch_size )
[INFO]: (generate_comp_tensors) Generated 512 tensors
For instance, a circuit tensor alongside parameters the model generated looks like this
print(out_tensor[0])
print(params[0])
tensor([[ 0, 0, 8, 0, -2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
[ 0, 0, 0, 4, 2, 3, 1, 8, 0, 8, 8, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
[ 0, 8, 8, 4, 0, -3, 0, 0, 0, 0, 8, 1, 8, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
[ 1, 8, 0, 0, 0, -3, 0, 8, 4, 8, 0, 0, 8, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]], device='cuda:0')
tensor([[ 0.0000, -0.7021, -0.9835, 0.0000, 0.0000, 0.0000, 0.0000, -0.9720, 0.0000, 0.6553, 0.2625, 0.0000, -0.7555, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]], device='cuda:0')
Evaluate and plot circuits
We decode these now to circuits and calculate their unitaries.
= decode_tensors_to_backend(simulator, tokenizer, out_tensor, params)
generated_qc_list, _ = get_unitaries(simulator, generated_qc_list) generated_us
We then evaluate the unitary infidelity to our target U
.
= UnitaryInfidelityNorm.distance(
U_norms =torch.from_numpy(np.stack(generated_us)).to(torch.complex128),
approx_U=U.unsqueeze(0).to(torch.complex128),
target_U )
We get the following distribution of the infidelities.
=(7, 3), constrained_layout=True)
plt.figure(figsize=13)
plt.xlabel(UnitaryInfidelityNorm.name(), fontsize"Frequency", fontsize=13)
plt.ylabel(=60)
plt.hist(U_norms, bins-0.05, 1.05])
plt.xlim([ plt.show()
We plot the four best ciruits, w.r.t. the infidelity:
= 4
plot_k_best
= np.argsort(U_norms)
idx = plt.subplots(1, plot_k_best, figsize=(10, 2), constrained_layout=True, dpi=150)
fig, axs
for i, (idx_i, ax) in enumerate(zip(idx[:plot_k_best], axs.flatten())):
ax.clear()"mpl", plot_barriers=False, ax=ax)
generated_qc_list[idx_i].draw(f"The {i+1}. best circuit: \n infidelity {U_norms[idx_i]:0.1e}.", fontsize=10) ax.set_title(
Gate-Pair tokenization
Now we want to extract reusable substructures (gadgets) from generated circuits. We use all generated tensors in out_tensor
, regardless if their circuits have good or bad infidelity.
= gpe.GatePairTokenizer(unique_class_values=pipeline.embedder.unique_class_values,
gate_pair_tokenizer =0,
zero_token=9,
padding_token="cpu") device
Next, we run our proposed Gate-Pair Encoding (GPE) scheme:
= gate_pair_tokenizer.learn(out_tensor.cpu(), max_depth=5, max_iters=100) _
New depth reached 1
New depth reached 2
New depth reached 3
break: max_iters reached
Now we plot the extracted tokens.
= 4
max_depth = 5 topk
= \
unpacked_vocab_configs_depths, unpacked_vocab_configs_cnts_depths =True) gpe.get_topk_depth_unpacked(gate_pair_tokenizer, num_of_qubits, use_raw
= min(max_depth, max(unpacked_vocab_configs_depths.keys()))
max_depth = plt.subplots(max_depth, topk, figsize=(12, 6), dpi=200)
fig, axs
for ax in axs.flatten():
ax.clear()
ax.set_axis_off()
for (depth, unpacked_vocab_configs), (unpacked_vocab_configs_cnts), axs_sel in \
zip(unpacked_vocab_configs_depths.items(), unpacked_vocab_configs_cnts_depths.values(), axs):
if depth > max_depth:
break
for i, (ax, unpacked_vocab_config, unpacked_vocab_config_cnt) in \
enumerate(zip(axs_sel, unpacked_vocab_configs, unpacked_vocab_configs_cnts)):
= torch.zeros((1, unpacked_vocab_config.shape[-1])) - 1
zero_ps = tokenizer.decode(unpacked_vocab_config, zero_ps)
instr = simulator.genqc_to_backend(instr, place_barriers=False)
qc
#------
ax.clear() "mpl",
qc.draw(=False,
plot_barriers=ax,
ax=False)
idle_wires
for text in ax.texts:
if 'q' in text.get_text():
False)
text.set_visible(
text.remove()
'none')
ax.patch.set_facecolor(0].set_color("none")
ax.patches[
f"Occurrences: {unpacked_vocab_config_cnt.item()}", fontsize=6)
ax.set_title(if i==0:
-0.03, 1-(depth-0.7)/max_depth, f"Depth {depth}:", horizontalalignment='left', verticalalignment='top', fontsize=12)
plt.figtext(
plt.tight_layout() plt.show()
As we only extract discrete tokens, the parameters of the continuous gates are set to 0 for plotting.
import genQC
print("genQC Version", genQC.__version__)
genQC Version 0.2.0